﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Test
{
	public class ConstantExpressionRewriter : ExpressionVisitor
	{
		Expression root;
		IEnumerable<ConstantExpression> constants;
		Dictionary<Type, ConstantExpression> map;
		BinaryExpression trueNode;
		public ConstantExpressionRewriter(Expression root, IEnumerable<ConstantExpression> constants) {
			this.constants = constants;
			this.root = root;
			this.map = new Dictionary<Type, ConstantExpression>();
			foreach (ConstantExpression c in constants) {
				Type elementType = TypeHelper.GetElementType(c.Type);
				this.map.Add(elementType, c);
			}
			this.trueNode = Expression.MakeBinary(ExpressionType.Equal, Expression.Constant(1), Expression.Constant(1));
		}

		public Expression Rewrite() {
			return this.Visit(this.root);
		}

		public override Expression Visit(Expression node) {
			return base.Visit(node);
		}

		protected override Expression VisitMethodCall(MethodCallExpression node) {
			if (node.Method.ReturnType.IsBoolean()) {				
				return trueNode;
			}
			return base.VisitMethodCall(node);
		}


		protected override Expression VisitBinary(BinaryExpression node) {
			/*	if the method returns a scalar, Boolean, or othersise non-tuple value, 
			 *	then this branch has no chance of returning objects (IDs) to fill the BinaryExpression, 
			 *	so splce out the branch from the LINQ query Expression, as it is assumed this branch was already applied in T-SQL.
			 */
			MethodCallExpression callNode = null;
			if (node.Left.NodeType == ExpressionType.Call) {
				callNode = node.Left as MethodCallExpression;
				if (node.NodeType.IsLogicalOperator()) {
					return Expression.MakeBinary(node.NodeType, this.trueNode, node.Right);
				}
				return this.trueNode;
			}
			else if (node.Right.NodeType == ExpressionType.Call) {
				callNode = node.Right as MethodCallExpression;
				if (node.NodeType.IsLogicalOperator()) {
					return Expression.MakeBinary(node.NodeType, node.Left, this.trueNode);
				}
				return this.trueNode;
			}

			//may not be necessary, since the fact that the node is a Call and part of a BinaryExpression implies that the return type is scalar.
			//if (callNode != null) {
			//    switch (callNode.Method.ReturnType.IsScalar()) {			
			//        case "Any":
			//        case "All":
			//        case "Min":
			//        case "Max":
			//        case "Average":
			//        case "Sum":
			//        case "Count":
			//            return trueNode;
			//    }
			//}						

			return base.VisitBinary(node);
		}

		protected override Expression VisitNew(NewExpression node) {
			Type elementType;
			if (this.IsQuery(node, out elementType)) {
				return ReplaceNode(elementType);
			}
			return base.VisitNew(node);
		}


		protected override Expression VisitConstant(ConstantExpression node) {
			Type elementType;
			if (this.IsQuery(node, out elementType)) {
				return ReplaceNode(elementType);
			}
			return base.VisitConstant(node);
		}

		ConstantExpression ReplaceNode(Type elementType) {
			IQueryable replacement;
			if (map.ContainsKey(elementType)) {
				replacement = ((System.Collections.IEnumerable)this.map[elementType].Value).AsQueryable();
			}
			else {
				replacement = MakeIQueryableInstance(elementType);
			}
			return Expression.Constant(replacement);
		}

		IQueryable MakeIQueryableInstance(Type elementType) {
			LambdaExpression lambda;
			Delegate del;
			object array;
			MethodCallExpression call;

			NewArrayExpression newarrayexpr = Expression.NewArrayInit(elementType);
			lambda = Expression.Lambda(newarrayexpr);
			del = lambda.Compile();
			array = del.DynamicInvoke();

			ParameterExpression p = Expression.Parameter(typeof(System.Collections.IEnumerable), "p");
			call = Expression.Call(typeof(Queryable), "AsQueryable", new Type[] { elementType }, Expression.Constant(array));
			lambda = Expression.Lambda(call, p);
			del = lambda.Compile();
			IQueryable result = (IQueryable)lambda.Compile().DynamicInvoke(array);
			return result;
		}

	}
}
